// Matlab Interface Library
// by Emanuele Ruffaldi 2002
// http://www.sssup.it/~pit/
// mailto:pit@sssup.it
//
// Description: XLTable support (read/write) for faster MATLAB DDE
using System;
using System.Text;
using System.IO;

namespace lCash
{
    /// <summary>
    /// A class for Excel &lt;--&gt; matrix
    /// </summary>
    public class XLTable
    {
        public enum Type
        {
            Unk = 0xFF,
            EOF = 0xF0,
            Table = 0x10,	// 4
            Float = 0x01,	// 8
            String = 0x02,	// 1+N
            Bool = 0x03,	// 2
            Error = 0x04,	// 2
            Blank = 0x05,	// 2
            Int = 0x06,		// 2
            Skip = 0x07,		// 2
        }

        public XLTable(byte[] data)
        {
            this.data = data;
            pos = 0;
            blockstart = 0;
            blocksize = -4;
            br = new System.IO.BinaryReader(new System.IO.MemoryStream(data));

            if (data.Length > 60000)
            {
                System.IO.FileStream fo = new System.IO.FileStream("c:\\xx.hex", System.IO.FileMode.OpenOrCreate,
                    System.IO.FileAccess.Write);
                fo.Write(data, 0, data.Length);
                fo.Close();
            }

            Type type = NextBlock();

            if (type == Type.Table)
            {
                rows = ExtractShort();
                cols = ExtractShort();
            }
        }

        static void NextIndices(ref int i, ref int j, int n)
        {
            if (++j >= n)
            {
                j = 0;
                i++;
            }
        }

        // don't use this to handle MATLAB matrices
        public static bool BytesToMatrix(byte[] tab, ref double[,] mtx, ref string[,] strmtx)
        {
            XLTable xt = new XLTable(tab);
            if (mtx == null || xt.Rows != mtx.GetLength(0) || xt.Columns != mtx.GetLength(1))
                mtx = new double[xt.Rows, xt.Columns];
            if (strmtx == null || xt.Rows != strmtx.GetLength(0) || xt.Columns != strmtx.GetLength(1))
                strmtx = new string[xt.Rows, xt.Columns];
            int nr = xt.Rows;
            int nc = xt.Columns;
            int i = 0, j = 0;
            Type bt;
            while ((bt = xt.NextBlock()) != Type.EOF)
            {
                switch (bt)
                {
                    case Type.Error:
                        while (xt.Next())
                        {
                            mtx[i, j] = 0;
                            NextIndices(ref i, ref j, nc);
                        }
                        break;

                    case Type.Float:

                        for (int k = 0; k < xt.BlockItems; k++)
                        {
                            mtx[i, j] = xt.Stream.ReadDouble();
                            if (++j >= nc)
                            {
                                j = 0;
                                i++;
                            }
                        }
                        break;
                    case Type.Int:
                    case Type.Bool:
                        while (xt.Next())
                        {
                            mtx[i, j] = xt.IntValue;
                            NextIndices(ref i, ref j, nc);
                        }
                        break;
                    case Type.Skip:
                    case Type.Blank:
                        while (xt.Next())
                        {
                            int n = xt.IntValue;
                            for (int q = 0; q < n; q++)
                            {
                                mtx[i, j] = 0;
                                NextIndices(ref i, ref j, nc);
                            }
                        }
                        break;
                    case Type.String:
                        while (xt.Next())
                        {
                            try
                            {
                                strmtx[i, j] = null;
                                mtx[i, j] = Double.Parse(xt.StringValue);
                            }
                            catch (Exception)
                            {
                                mtx[i, j] = 0;
                                strmtx[i, j] = xt.StringValue;
                            }
                            NextIndices(ref i, ref j, nc);
                        }
                        break;
                    default:
                        break;
                }
            }
            return true;
        }

        public static void WriteBlockHeader(System.IO.BinaryWriter bw, Type t, int size)
        {
            bw.Write((short)t);
            bw.Write((short)size);
        }

        // it's easier and we know the capacity
        // don't use this to handle MATLAB matrices
        public static bool MatrixToBytes(double[,] mtx, out byte[] tab)
        {
            int nr = mtx.GetLength(0);
            int nc = mtx.GetLength(1);
            tab = null;
            // calculate precisely the size of the buffer:
            // nr*nc*8 + 8 (table) + 4*(number of float headers)			
            int total = nr * nc;
            int floatsInBlock = (57856) / 8;
            int blocks = total / floatsInBlock + 1;
            if (total % floatsInBlock == 0)
                blocks--;
            tab = new byte[total * 8 + 8 + 4 * blocks];
            System.IO.MemoryStream ms = new System.IO.MemoryStream(tab);
            System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms);

            // write table header
            WriteBlockHeader(bw, Type.Table, 2 + 2);
            bw.Write((short)nr);
            bw.Write((short)nc);

            // cut the whole block of data into blocks lesserthan 32K
            int done = 0;
            int n;
            int q = 0;

            n = total - done;
            if (n > floatsInBlock)
                n = floatsInBlock;
            WriteBlockHeader(bw, Type.Float, n * 8);
            for (int i = 0; i < nr; i++)
            {
                for (int j = 0; j < nc; j++, q++)
                {
                    if (q >= n)
                    {
                        done += n;
                        n = (total - done);
                        if (n > floatsInBlock)
                            n = floatsInBlock;
                        q = 0;
                        WriteBlockHeader(bw, Type.Float, n * 8);
                    }
                    bw.Write(mtx[i, j]);
                }
            }
            bw.Close();

            if (tab.Length > 60000)
            {
                System.IO.FileStream fo = new System.IO.FileStream("c:\\yy.hex", System.IO.FileMode.Create,
                    System.IO.FileAccess.Write);
                fo.Write(tab, 0, tab.Length);
                fo.Close();
            }
            return true;
        }

        public int IntValue
        {
            get { return intValue; }
        }

        public double FloatValue
        {
            get { return floatValue; }
        }

        public string StringValue
        {
            get { return strValue; }
        }

        internal double ExtractDouble()
        {
            return br.ReadDouble();
        }

        internal int ExtractShort()
        {
            int s = br.ReadUInt16();
            pos += 2;
            return s;
        }

        public Type NextBlock()
        {
            // scan 
            pos = blockstart + blocksize + 4;

            if (pos >= data.Length)
                return Type.EOF;
            blockstart = pos;
            br.BaseStream.Seek(pos, System.IO.SeekOrigin.Begin);
            int type = ExtractShort();
            blocksize = ExtractShort();

            switch (type)
            {
                case (short)Type.Blank: currentType = Type.Blank; blockitems = blocksize / 2; break;
                case (short)Type.Bool: currentType = Type.Bool; blockitems = blocksize / 2; break;
                case (short)Type.Error: currentType = Type.Error; blockitems = blocksize / 2; break;
                case (short)Type.Float: currentType = Type.Float; blockitems = blocksize / 8; break;
                case (short)Type.Int: currentType = Type.Int; blockitems = blocksize / 2; break;
                case (short)Type.Skip: currentType = Type.Skip; blockitems = blocksize / 2; break;
                case (short)Type.String: currentType = Type.String; blockitems = -1; break;
                case (short)Type.Table: currentType = Type.Table; blockitems = -1; break;
                default:
                    currentType = Type.Unk;
                    break;
            }
            return currentType;
        }

        bool Next()
        {
            if (pos >= blockstart + blocksize + 4)
                return false;
            switch (currentType)
            {
                case Type.Int:
                case Type.Skip:
                case Type.Blank:
                case Type.Error:
                case Type.Bool:
                    intValue = ExtractShort();
                    break;
                case Type.Float:
                    floatValue = ExtractDouble();
                    break;
                case Type.String:
                    {
                        int n = br.ReadByte();
                        pos++;
                        if (n == 0)
                            strValue = "";
                        else
                        {
                            //strValue = ASCIIEncoding.ASCII.GetString(data, pos, n);
                            System.Text.Encoding encoding = System.Text.Encoding.GetEncoding(codepage);
                            strValue = encoding.GetString(data, pos, n);
                            br.BaseStream.Seek(n, System.IO.SeekOrigin.Current);
                            pos += n;
                        }
                        break;
                    }
                case Type.Table:
                    intValue = ExtractShort(); break;
                default:
                    return false;
            }
            return true;
        }

        public System.IO.BinaryReader Stream
        {
            get { return br; }
        }

        public int Columns
        {
            get { return cols; }
        }

        public int Rows
        {
            get { return rows; }
        }

        public int BlockItems
        {
            get { return blockitems; }
        }

        int intValue;
        double floatValue;
        string strValue;
        Type currentType;
        int blockstart, blocksize;
        int blockitems;
        int pos;
        byte[] data;
        int cols, rows;
        public int codepage = 1251;

        System.IO.BinaryReader br;
    }
}